package impl

import (
	"context"
	"errors"
	"fmt"
	"github.com/redis/go-redis/v9"
	"github.com/starmotioncf/common/constant"
	"github.com/starmotioncf/common/log"
	"github.com/starmotioncf/common/restriction"
	"github.com/starmotioncf/common/utils"
	"github.com/starmotioncf/soutchatinternal/constant/redis_key"
	"github.com/starmotioncf/soutchatinternal/model"
	"github.com/starmotioncf/soutchatinternal/repository"
	"go.uber.org/zap"
	"time"

	// redis2 "github.com/yuki17/hoby-service-common/redis"
	redis2 "github.com/starmotioncf/common/redis"

	"gorm.io/gorm"
)

type restrictionRepository struct {
	*repository.Repository
}

func NewRestrictionRepository(r *repository.Repository) repository.RestrictionRepository {
	return &restrictionRepository{
		Repository: r,
	}
}

// AddUidToBlackList 添加uid到黑名单
func (r *restrictionRepository) AddUidToBlackList(ctx context.Context, uid uint64, opts ...repository.RestrictionOpt) (*model.Restriction, error) {
	if uid == 0 {
		return nil, nil
	}
	return r.AddBlackList(ctx, restriction.TypUid, fmt.Sprintf("%d", uid), opts...)
}

// AddIpToBlackList 添加ip到黑名单
func (r *restrictionRepository) AddIpToBlackList(ctx context.Context, ip string, opts ...repository.RestrictionOpt) (*model.Restriction, error) {
	if ip == "" || ip == "from-web" {
		return nil, nil
	}
	//cf的ip不封禁
	if utils.CheckIsCfIp(ip) {
		return nil, nil
	}
	return r.AddBlackList(ctx, restriction.TypIp, ip, opts...)
}

// AddMacToBlackList 添加mac到黑名单
func (r *restrictionRepository) AddMacToBlackList(ctx context.Context, mac string, opts ...repository.RestrictionOpt) (*model.Restriction, error) {
	if mac == "" || mac == "from-web" {
		return nil, nil
	}
	return r.AddBlackList(ctx, restriction.TypMac, mac, opts...)
}

// AddBlackList 添加到黑名单
func (r *restrictionRepository) AddBlackList(ctx context.Context, typ restriction.Typ, val string, opts ...repository.RestrictionOpt) (*model.Restriction, error) {
	return r.AddRecord(ctx, restriction.ModeBlacklist, typ, val, opts...)
}

// AddUidToWhiteList 添加Uid到白名单
func (r *restrictionRepository) AddUidToWhiteList(ctx context.Context, uid uint64, opts ...repository.RestrictionOpt) (*model.Restriction, error) {
	if uid == 0 {
		return nil, nil
	}
	return r.AddWhiteList(ctx, restriction.TypUid, fmt.Sprintf("%d", uid), opts...)
}

// AddIpToWhiteList 添加IP到白名单
func (r *restrictionRepository) AddIpToWhiteList(ctx context.Context, ip string, opts ...repository.RestrictionOpt) (*model.Restriction, error) {
	if ip == "" || ip == "from-web" {
		return nil, nil
	}
	//cf的ip不封禁
	if utils.CheckIsCfIp(ip) {
		return nil, nil
	}
	return r.AddWhiteList(ctx, restriction.TypIp, ip, opts...)
}

// AddMacToWhiteList 添加mac到白名单
func (r *restrictionRepository) AddMacToWhiteList(ctx context.Context, mac string, opts ...repository.RestrictionOpt) (*model.Restriction, error) {
	if mac == "" || mac == "from-web" {
		return nil, nil
	}
	return r.AddWhiteList(ctx, restriction.TypMac, mac, opts...)
}

// AddWhiteList 添加到白名单
func (r *restrictionRepository) AddWhiteList(ctx context.Context, typ restriction.Typ, val string, opts ...repository.RestrictionOpt) (*model.Restriction, error) {
	return r.AddRecord(ctx, restriction.ModeWhitelist, typ, val, opts...)
}

func (r *restrictionRepository) AddRecord(ctx context.Context, mode restriction.Mode, typ restriction.Typ, val string, opts ...repository.RestrictionOpt) (*model.Restriction, error) {
	ts := time.Now().Unix()
	fr := model.RestrictionRecord{
		Mode:      mode,
		Typ:       typ,
		Val:       val,
		CreatedAt: ts,
	}
	for _, opt := range opts {
		opt(&fr)
	}
	//为0 永久期限
	if fr.Deadline == 0 {
		fr.Deadline = constant.Permanent
	}
	//默认封禁所有
	if fr.Behaviors == 0 {
		fr.Behaviors = restriction.AllBehaviors
	}
	return r.createRecord(ctx, &fr)
}

// getRecordFromCache 从缓存里获取封禁记录
// 注意，后台操作封禁的时候，一定要把封禁记录写入redis
func (r *restrictionRepository) getRecordFromCache(ctx context.Context, typ restriction.Typ, val string) *model.Restriction {
	if val == "" {
		return nil
	}
	rk := redis_key.Restriction.ToString(typ)
	res := r.RDB().HGet(ctx, rk, val)
	if res.Err() != nil {
		if !errors.Is(res.Err(), redis.Nil) && !errors.Is(res.Err(), context.Canceled) {
			log.WithContext(ctx).Error("Restriction get record err:", zap.Int64("typ", int64(typ)), zap.String("val", val), zap.Error(res.Err()))
		}
		return nil
	}
	m := model.Restriction{}
	if err := res.Scan(&m); err != nil {
		log.WithContext(ctx).Error("Restriction redis Scan err:", zap.Int64("typ", int64(typ)), zap.String("val", val), zap.Error(res.Err()))
		return nil
	}
	//已经到了封禁截止时间
	if m.BanRemainingTime() <= 0 {
		return nil
	}
	return &m
}

func (r *restrictionRepository) createRecord(ctx context.Context, m *model.RestrictionRecord) (cacheModel *model.Restriction, err error) {
	defer func() {
		if err != nil && !errors.Is(err, redis2.ErrTimeout) {
			log.WithContext(ctx).Sugar().Errorf("创建限制记录失败", err.Error())
		}
	}()
	nowTs := time.Now().Unix()
	//封禁截止时间至少大于当前
	if m.Deadline <= nowTs {
		err = errors.New("restriction deadline fail")
		return
	}
	//并发锁
	locker := redis2.NewLocker(redis_key.RestrictionLock.ToString(m.Typ, m.Val), r.RDB())
	if err = locker.TryLock(time.Second * 1); err != nil {
		return
	}
	//取封禁最长的时间为准，不累加
	if m.Deadline != constant.Permanent {
		if fr := r.getRecordFromCache(ctx, m.Typ, m.Val); fr != nil {
			if fr.Deadline > m.Deadline {
				m.Deadline = fr.Deadline
			}
		}
	}
	if m.Deadline > constant.Permanent {
		m.Deadline = constant.Permanent
	}
	//插入记录
	err = r.DB(ctx).Create(&m).Error
	if err == nil {
		cacheModel = m.ToRestriction()
		_err := r.DB(ctx).Model(&model.RestrictionRecord{}).
			Where("typ = ? and val = ? and deadline > ? and id != ? and status = 1", m.Typ, m.Val, nowTs, m.ID).
			Update("status", 3).Error
		if _err != nil {
			log.WithContext(ctx).Error(fmt.Sprintf("Restriction addRecord[%d] update status err[%s]:%s", m.ID, m.Val, _err.Error()))
		}
		//插入redis
		rk := redis_key.Restriction.ToString(m.Typ)
		rRes := r.RDB().HSet(ctx, rk, m.Val, cacheModel)
		if rRes.Err() != nil {
			log.WithContext(ctx).Sugar().Errorf(fmt.Sprintf("Restriction addRecord[%d] add cache err[%s]:%s", m.ID, m.Val, rRes.Err().Error()))
		} else {
			//设置过期时间，也就是封禁到期的时间
			r.RDB().HExpireAt(ctx, rk, cacheModel.DeadlineTime(), m.Val)
		}
	}
	return
}

func (r *restrictionRepository) GetRecord(ctx context.Context, typ restriction.Typ, val string) *model.Restriction {
	return r.getRecordFromCache(ctx, typ, val)
}

func (r *restrictionRepository) RemoveRecord(ctx context.Context, id int64, updateBy uint64) (err error) {
	if id == 0 {
		return nil
	}
	var record *model.RestrictionRecord
	err = r.DB(ctx).Model(&model.RestrictionRecord{}).Where("id = ? and status = 1", id).Find(&record).Error
	if err != nil {
		return
	}
	err = r.DB(ctx).Model(&model.RestrictionRecord{}).Where("id = ?", id).Updates(map[string]any{
		"updated_at": time.Now().Unix(),
		"update_by":  updateBy,
		"status":     2,
	}).Error
	if err == nil {
		_err := r.RDB().HDel(ctx, redis_key.Restriction.ToString(record.Typ), record.Val).Err()
		if _err != nil {
			log.WithContext(ctx).Error("RemoveRecord HDel error", zap.Error(_err))
		}
	}
	return err
}

func (r *restrictionRepository) GetById(ctx context.Context, id int64) (m *model.RestrictionRecord, err error) {
	if id == 0 {
		return nil, gorm.ErrRecordNotFound
	}
	err = r.DB(ctx).Model(&model.RestrictionRecord{}).Where("id = ?", id).Find(&m).Error
	return
}
